// David Eberly, Geometric Tools, Redmond WA 98052
// Copyright (c) 1998-2024
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
// Version: 6.5.2023.08.08

#pragma once

// The 'vertices' are a vertex pool that contain the vertices of the mesh. The
// 'indices' are index pairs into 'vertices', each pair representing a segment
// of the polysegment. The last constructor does not require that all elements
// of the vertex pool participate in the polysegment. The polysegment segments
// are unrestricted. They can form an open or closed polysegment. They can
// intersect at interior points of the segments. A segment can be degenerate
// whereby its endpoints are equal.

#include <Mathematics/Logger.h>
#include <Mathematics/Vector.h>
#include <array>
#include <cstddef>
#include <cstdint>
#include <vector>

namespace gte
{
    template <int32_t N, typename T>
    class SegmentMesh
    {
    public:
        // The comments for topology are based on the input vertices having
        // L >= 2 elements ordered { V[0], V[1], ..., V[L-1] }.
        enum class Topology
        {
            // The default constructor has no information about the mesh
            // topology.
            UNKNOWN,

            // L must be an even number. The L/2 segments are
            // <V[0],V[1]>, <V[2],V[3]>, ..., <V[L-2],V[L-1]>.
            DISJOINT,

            // The vertices form an open polyline. The L-1 segments are
            // <V[0],V[1]>, <V[1],V[2]>, ..., <V[L-2],V[L-1]>.
            CONTIGUOUS_OPEN,

            // The vertices form a closed polyline. The L segments are
            // <V[0],V[1]>, ..., <V[L-2],V[L-1]>, <V[L-1],V[0]>.
            CONTIGUOUS_CLOSED,

            // The segments are generated by indexing into the vertex array.
            // The mesh has M segments, each segment generated by an index
            // pair S[i] for 0 <= i <= M-1. The segments are
            // <V[S[0][0],S[0][1]>, <V[S[1][0],S[1][1]>, ...,
            // <V[S[M-1][0],S[M-1][1]>. The other topologies can be
            // represented using the indexed approach,
            //   DISJOINT: S[i] = {2*i, 2*i+1} for 0 <= i <= (L-2)/2
            //   CONTIGUOUS_OPEN: S[i] = {i, i + 1} for 0 <= i <= L-2
            //   CONTIGUOUS_CLOSED: S[i] = {i, (i + 1) % L} for 0 <= i <= L-1
            INDEXED
        };

        SegmentMesh()
            :
            mTopology(Topology::UNKNOWN),
            mVertices{},
            mIndices{}
        {
        }

        // Create a mesh with DISJOINT topology.
        SegmentMesh(std::vector<Vector<N, T>> const& vertices)
            :
            mTopology(Topology::DISJOINT),
            mVertices(vertices),
            mIndices{}
        {
            LogAssert(
                vertices.size() >= 2,
                "Invalid number of vertices.");

            mIndices.resize(vertices.size() / 2);
            for (size_t i = 0; i < mIndices.size(); ++i)
            {
                mIndices[i][0] = 2 * i;
                mIndices[i][1] = mIndices[i][0] + 1;
            }
        }

        // Create a mesh with CONTIGUOUS topology. Set isOpen to 'true' for
        // CONTIGUOUS_OPEN topology or 'false' for CONTIGUOUS_CLOSED
        // topology.
        SegmentMesh(std::vector<Vector<N, T>> const& vertices, bool isOpen)
            :
            mTopology(Topology::UNKNOWN),
            mVertices(vertices),
            mIndices{}
        {
            LogAssert(
                vertices.size() >= 2,
                "Invalid number of vertices.");

            if (isOpen)
            {
                mTopology = Topology::CONTIGUOUS_OPEN;
                mIndices.resize(vertices.size() - 1);
                for (size_t i0 = 0, i1 = 1; i0 < mIndices.size(); i0 = i1++)
                {
                    mIndices[i0][0] = i0;
                    mIndices[i0][1] = i1;
                }
            }
            else
            {
                mTopology = Topology::CONTIGUOUS_CLOSED;
                mIndices.resize(vertices.size());
                for (size_t i0 = mIndices.size() - 1, i1 = 0; i1 < mIndices.size(); i0 = i1++)
                {
                    mIndices[i1][0] = i0;
                    mIndices[i1][1] = i1;
                }
            }
        }

        // Create a mesh with INDEXED topology. If the caller is certain that
        // the indices satisfy indices[i] < vertices.size() for all i, set
        // validateIndices to 'false'. Otherwise, set validateIndices to 'true'
        // so that the vertices are validated internally.
        SegmentMesh(std::vector<Vector<N, T>> const& vertices,
            std::vector<std::array<size_t, 2>> indices, bool validateIndices)
            :
            mTopology(Topology::INDEXED),
            mVertices(vertices),
            mIndices(indices)
        {
            LogAssert(
                vertices.size() >= 2 && indices.size() >= 1,
                "Invalid number of vertices or indices.");

            if (validateIndices)
            {
                for (size_t i = 0; i < indices.size(); ++i)
                {
                    LogAssert(
                        indices[i][0] < vertices.size() && indices[i][1] < vertices.size(),
                        "Invalid index into vertex array.");
                }
            }
        }

        // Member access. For now the vertices and indices are read-only.
        // TODO: Allow for dynamic creation of meshes. This requires a
        // redesign that should also be used for implementing a similar class
        // for triangle mesh primitives.
        Topology GetTopology() const
        {
            return mTopology;
        }

        std::vector<Vector<N, T>> const& GetVertices() const
        {
            return mVertices;
        }

        std::vector<std::array<size_t, 2>> const& GetIndices() const
        {
            return mIndices;
        }

    private:
        Topology mTopology;
        std::vector<Vector<N, T>> mVertices;
        std::vector<std::array<size_t, 2>> mIndices;
    };

    // Template aliases for convenience.
    template <typename Real>
    using SegmentMesh2 = SegmentMesh<2, Real>;

    template <typename Real>
    using SegmentMesh3 = SegmentMesh<3, Real>;
}
